Passed
Push — master ( af51de...9f0799 )
by Apocist
37s
created

nodeVBulletinAPI.js ➔ ... ➔ ???   C

Complexity

Conditions 9
Paths 231

Size

Total Lines 73
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 9
eloc 43
c 3
b 0
f 0
nc 231
nop 2
dl 0
loc 73
rs 6.5146

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
'use strict';
2
const md5 = require('js-md5'),
3
    request = require('request'),
4
    url = require('url'),
5
    uuidV1 = require('uuid/v1'),
6
    _ = require('underscore'),
7
    Forum = require('./Forum'),
8
    Inbox = require('./Inbox'),
9
    Member = require('./Member'),
10
    Message = require('./Message'),
11
    Post = require('./Post'),
12
    Thread = require('./Thread'),
13
    {version} = require('./package.json');
14
15
/**
16
 *
17
 */
18
class VBApi {
19
    /**
20
     * Initialize a vb api connection .This needs to be called for the first time
21
     * @param {string} apiUrl
22
     * @param {string} apiKey
23
     * @param {string} platformName
24
     * @param {string} platformVersion
25
     */
26
    constructor(apiUrl, apiKey, platformName, platformVersion) {
27
        /**
28
         * The current status of the API connection to vBulletin server
29
         * @type {boolean}
30
         */
31
        this.initialized = false;
32
        let urlParts = url.parse(apiUrl);
33
        /**
34
         * @type {{baseUrl: string, apiUrl: (*|string), apiKey: (*|string), clientName: string, platformVersion: string, clientVersion, platformName: string, uniqueId: *}}
35
         * @private
36
         */
37
        this.__connectionVars = {
38
            baseUrl: `${urlParts.protocol}//${urlParts.hostname}/`,
39
            apiUrl: apiUrl || '',
40
            apiKey: apiKey,
41
            clientName: 'nodeVBulletinAPI',
42
            clientVersion: version,
43
            platformName: platformName || '',
44
            platformVersion: platformVersion || '',
45
            uniqueId: md5('nodeVBulletinAPI' + version + platformName + platformVersion + uuidV1()),
46
        };
47
48
        /**
49
         * @type {{apiVersion: string, apiAccessToken: string, sessionHash: string, apiClientId: string, secret: string, error: null||string}}
50
         * @private
51
         */
52
        this.__clientSessionVars = {
53
            apiVersion: '',
54
            apiAccessToken: '',
55
            sessionHash: '',
56
            apiClientId: '',
57
            secret: '',
58
            error: null
59
        };
60
61
        /**
62
         * @typedef UserVars
63
         * @property {string} dbsessionhash
64
         * @property {number} userid
65
         * @property {string} username
66
         * @property {boolean} loggedIn
67
         * @type {UserVars}
68
         */
69
        this.userSessionVars = {
70
            dbsessionhash: '',
71
            username: '',
72
            userid: 0,
73
            loggedIn: false
74
        };
75
76
        /** @private */
77
        this.__waitingForInitializationCallback = function () {
78
        }; // A blank callback to be filled in
79
80
        if (
81
            this.__connectionVars.apiKey !== ''
82
            && this.__connectionVars.apiUrl !== ''
83
            && this.__connectionVars.platformName !== ''
84
            && this.__connectionVars.platformVersion !== ''
85
        ) {
86
            this.__initialize();
87
        } else {
88
            this.__clientSessionVars.error = 'apiInit(): Initialization requires a `apiUrl`, `apiKey`, `platformName`, and `platformVersion`';
89
            this.__waitingForInitializationCallback(false);
90
        }
91
    }
92
93
    /**
94
     * Initialize a vb api connection. This needs to be called for the first time
95
     * @private
96
     */
97
    __initialize() {
98
        let that = this;
99
        // Run itself as a self invoked promise that is awaited by nothing. callMethod shall wait until this is finished
100
        (async function __initialize_self() {
101
            let error = null;
102
            let result = null;
103
104
            try {
105
                /**
106
                 *
107
                 * @type {{}}
108
                 * @property {string} apiversion
109
                 * @property {string} apiaccesstoken
110
                 * @property {string} sessionhash
111
                 * @property {string} apiclientid
112
                 * @property {string} secret
113
                 */
114
                let response = await that.callMethod({
115
                    method: 'api_init',
116
                    params: {
117
                        clientname: that.__connectionVars.clientName,
118
                        clientversion: that.__connectionVars.clientVersion,
119
                        platformname: that.__connectionVars.platformName,
120
                        platformversion: that.__connectionVars.platformVersion,
121
                        uniqueid: that.__connectionVars.uniqueId
122
                    }
123
                });
124
125
                if (
126
                    response.apiversion
127
                    && response.apiaccesstoken
128
                    && response.sessionhash
129
                    && response.apiclientid
130
                    && response.secret
131
                ) {
132
                    that.__clientSessionVars = {
133
                        apiVersion: response.apiversion,
134
                        apiAccessToken: response.apiaccesstoken,
135
                        sessionHash: response.sessionhash,
136
                        apiClientId: response.apiclientid,
137
                        secret: response.secret,
138
                        error: null
139
                    };
140
                    that.initialized = true;
141
142
                    that.__waitingForInitializationCallback(true);
143
                    result = that;
144
                }
145
146
                if (result === null) {
147
                    that.__clientSessionVars.error = that.constructor.parseErrorMessage(response) || 'TODO ERROR (api connection did not return a session)';
148
                    that.__waitingForInitializationCallback(false);
149
                    error = that.__clientSessionVars.error;
150
                }
151
            } catch (e) {
152
                that.__clientSessionVars.error = e;
153
                that.__waitingForInitializationCallback(false);
154
                // reject(e);
155
                error = e;
156
            }
157
            return error || result;
158
        }());
159
    }
160
161
    /**
162
     * Will return after #initialize() is complete. Otherwise may reject() after 15 second timeout
163
     * @param {number=5} waitTime
164
     * @returns {Promise<void>}
165
     * @fulfill {void}
166
     * @reject {string} - Error Reason
167
     */
168
    async waitForInitialization(waitTime) {
169
        let that = this;
170
        waitTime = waitTime || 5;
171
        return new Promise(async function (resolve, reject) {
172
            if (that.initialized) {
173
                resolve();
174
            } else if (that.__clientSessionVars.error !== null) {
175
                reject(that.__clientSessionVars.error);
176
            } else {
177
                /**
178
                 * @type {number}
179
                 * @private
180
                 */
181
                that.__waitingForInitializationTimeout = setTimeout(
182
                    function () {
183
                        that.__waitingForInitializationCallback = function () {
184
                        }; // Set back to a blank function
185
                        if (that.initialized) {
186
                            resolve();
187
                        } else {
188
                            reject('Connection could not be achieved due to timed out', that.__clientSessionVars.error);
189
                        }
190
191
                    },
192
                    waitTime * 1000 // x second timeout
193
                );
194
                /**
195
                 * @param {boolean=true} success
196
                 * @private
197
                 */
198
                that.__waitingForInitializationCallback = function (success) {
199
                    if (that.__waitingForInitializationTimeout) {
200
                        clearTimeout(that.__waitingForInitializationTimeout);
201
                    }
202
                    if (success === false) {
203
                        reject(that.__clientSessionVars.error);
204
                    } else {
205
                        resolve();
206
                    }
207
                };
208
            }
209
        })
210
    }
211
212
    /**
213
     *
214
     * @param {object} options
215
     * @param {string} options.method - Required action to take
216
     * @param {object<string,string>} [options.params={}] - Optional parameter variables
217
     * @param {?object<string,string>} [options.cookies] - Optional cookie variables
218
     * @returns {Promise<{}>}
219
     * @fulfill {{}}
220
     * @reject {string} - Error Reason
221
     */
222
    async callMethod(options) {
223
        let that = this;
224
        let sign = true;
225
        options = options || {};
226
        options.params = options.params || {};
227
        return new Promise(async function (resolve, reject) {
228
            try {
229
                if (!options.method) {
230
                    reject('callMethod(): requires a supplied method');
231
                    return;
232
                }
233
234
                // Sign all calls except for api_init
235
                if (options.method === 'api_init') {
236
                    sign = false;
237
                }
238
239
                // await a valid session before continuing (skipping waiting on __initialize())
240
                if (sign === true) {
241
                    await that.waitForInitialization();
242
                }
243
244
                // Gather our sessions variables together
245
                let reqParams = {
246
                    api_m: options.method,
247
                    api_c: that.__clientSessionVars.apiClientId, //clientId
248
                    api_s: that.__clientSessionVars.apiAccessToken, //apiAccessToken (may be empty)
249
                    api_v: that.__clientSessionVars.apiVersion //api version
250
                };
251
                _.extend(reqParams, options.params); // Combine the arrays
252
253
                if (sign === true) {
254
                    // Generate a signature to validate that we are authenticated
255
                    if (that.initialized) {
256
                        reqParams.api_sig = md5(that.__clientSessionVars.apiAccessToken + that.__clientSessionVars.apiClientId + that.__clientSessionVars.secret + that.__connectionVars.apiKey);
257
                    } else {
258
                        reject('callMethod(): requires initialization. Not initialized');
259
                        return;
260
                    }
261
                }
262
263
                // Create a valid http Request
264
                let reqOptions = {
265
                    url: that.__connectionVars.apiUrl,
266
                    formData: reqParams,
267
                    headers: {
268
                        'User-Agent': that.__connectionVars.clientName
269
                    }
270
                };
271
272
                // Some command require adding a cookie, we'll do that here
273
                if (options.cookies) {
274
                    let j = request.jar();
275
                    for (let variable in options.cookies) {
276
                        if (options.cookies.hasOwnProperty(variable)) {
277
                            let cookieString = variable + '=' + options.cookies[variable];
278
                            let cookie = request.cookie(cookieString);
279
                            j.setCookie(cookie, that.__connectionVars.baseUrl);
280
                        }
281
                    }
282
                    reqOptions.jar = j;// Adds cookies to the request
283
                }
284
285
                request.post(
286
                    reqOptions,
287
                    function (error, response, body) {
288
                        if (!error && response.statusCode === 200) {
289
                            resolve(JSON.parse(body));
290
                        } else {
291
                            //console.log('No response');
292
                            reject('callMethod(): no response.');
293
                        }
294
                    }
295
                );
296
            } catch (e) {
297
                reject(e);
298
            }
299
        });
300
    }
301
302
    /**
303
     * Attempts to log in a user.
304
     * @param {string} username - Username
305
     * @param {string} password - clear text password
306
     * @param {object=} options
307
     * @param {string=} options.username - Ignore, already required at username
308
     * @param {string=} options.password - Ignore, already required at password
309
     * @returns {Promise<UserVars>}
310
     * @fulfill {UserVars}
311
     * @reject {string} - Error Reason. Expects:
312
     */
313
    async login(username, password, options) {
314
        options = options || {};
315
        options.username = username || options.username || '';
316
        options.password = md5(password || options.password || '');
317
        return await this.loginMD5('', '', options);
318
    }
319
320
    /**
321
     *
322
     * Attempts to log in a user. Requires the password to be pre md5 hashed.
323
     * @param {string} username - Username
324
     * @param {string} password - MD5 hashed password
325
     * @param {object=} options
326
     * @param {string=} options.username - Ignore, already required at username
327
     * @param {string=} options.password - Ignore, already required at password
328
     * @returns {Promise<UserVars>}
329
     * @fulfill {UserVars}
330
     * @reject {string} - Error Reason. Expects:
331
     */
332
    async loginMD5(username, password, options) {
333
        let that = this;
334
        options = options || {};
335
        options.username = username || options.username || {};
336
        options.password = password || options.password || {};
337
        return new Promise(async function (resolve, reject) {
338
            try {
339
                let response = await that.callMethod(
340
                    {
341
                        method: 'login_login',
342
                        params: {
343
                            vb_login_username: options.username || '',
344
                            vb_login_md5password: options.password || ''
345
                        }
346
                    }
347
                );
348
                /**
349
                 redirect_login - (NOT A ERROR) Login successful
350
                 badlogin - Username or Password incorrect. Login failed.
351
                 badlogin_strikes - Username or Password incorrect. Login failed. You have used {X} out of 5 login attempts. After all 5 have been used, you will be unable to login for 15 minutes.
352
                 */
353
                let error = that.constructor.parseErrorMessage(response);
354
                if (response.session) {
355
                    that.userSessionVars = response.session;
356
                    if (error === 'redirect_login') {
357
                        that.userSessionVars.username = options.username;
358
                        that.userSessionVars.loggedIn = true;
359
                    }
360
                }
361
                if (error === 'redirect_login') {
362
                    resolve(that.userSessionVars);
363
                } else {
364
                    reject(error);
365
                }
366
367
            } catch (e) {
368
                reject(e);
369
            }
370
        });
371
    }
372
373
    /**
374
     * Attempts to log the user out.
375
     * @returns {Promise<boolean>} - Returns true on success, otherwise error code is rejected
376
     * @fulfill {boolean}
377
     * @reject {string} - Error Reason
378
     */
379
    async logout() {
380
        let that = this;
381
        return new Promise(async function (resolve, reject) {
382
            let error;
383
            try {
384
                let response = await that.callMethod({
385
                    method: 'login_logout'
386
                });
387
                error = that.constructor.parseErrorMessage(response);
388
                if (response.session) {
389
                    that.userSessionVars = response.session;
390
                    if (error === 'cookieclear') {
391
                        that.userSessionVars.username = '';
392
                        that.userSessionVars.loggedIn = false;
393
                    }
394
                }
395
                if (error === 'cookieclear') {
396
                    error = null;
397
                }
398
            } catch (e) {
399
                reject(e);
400
            }
401
402
            if (error) {
403
                reject(error);
404
            } else {
405
                resolve(true)
406
            }
407
        });
408
    }
409
410
    /**
411
     *
412
     * @param {object} response - Response object from callMethod()
413
     * @returns {string || null} status - Error message
414
     */
415
    static parseErrorMessage(response) {
416
        let retur = '';
417
        if (
418
            response.hasOwnProperty('response')
419
            && response.response.hasOwnProperty('errormessage')
420
        ) {
421
            if (_.isArray(response.response.errormessage)) {
422
                retur = response.response.errormessage[0]
423
            } else {
424
                retur = response.response.errormessage;
425
            }
426
        }
427
        return retur;
428
    }
429
430
    /**
431
     * List every Forum and sub forum available to the user.
432
     * @returns {Promise<Forum[]>} - Array of Forum objects
433
     * @fulfill {Forum[]}
434
     * @reject {string} - Error Reason. Expects:
435
     */
436
    getForums() {
437
        return Forum.getHome(this);
438
    }
439
440
    /**
441
     * List detailed info about a forum and it's sub-forums and threads
442
     * @param {number} forumId - Forum id
443
     * @param {object=} options - Secondary Options
444
     * @param {number=} options.forumid - Ignore, already required at forumId
445
     * @returns {Promise<Forum>} - Returns a Forum object
446
     * @fulfill {Forum}
447
     * @reject {string} - Error Reason. Expects:
448
     */
449
    getForum(forumId, options) {
450
        return Forum.get(this, forumId, options);
451
    }
452
453
454
    /**
455
     * Attempts to submit a new Post into a specified Thread
456
     * @param {number} threadId - Thread id
457
     * @param {string} message - Post Message
458
     * @param {object=} options
459
     * @param {boolean=} options.signature  - Optionally append your signature
460
     * @param {number=} options.threadid - Ignore, already required at threadId
461
     * @param {string=} options.message - Ignore, already required at message
462
     * @returns {Promise<*>} - Returns a unhandled response currently
463
     * @fulfill {*}
464
     * @reject {string} - Error Reason. Expects:
465
     */
466
    createPost(threadId, message, options) {
467
        return Post.create(this, threadId, message, options);
468
    }
469
470
    /**
471
     * @deprecated as of 1.4.1
472
     * @see createPost
473
     */
474
    newPost(threadId, message, options) {
475
        return Post.create(this, threadId, message, options);
476
    }
477
478
    /**
479
     * Attempts to edit an existing Post
480
     * @param {number} postId - Post id
481
     * @param {string} message - Post Message
482
     * @param {object=} options
483
     * @param {string=} options.reason - Reason for editing
484
     * @param {boolean=} options.signature - Optionally append your signature
485
     * @param {number=} options.postid - Ignore, already required at postId
486
     * @param {string=} options.message - Ignore, already required at message
487
     * @returns {Promise<*>} - Returns a unhandled response currently
488
     * @fulfill {*}
489
     * @reject {string} - Error Reason. Expects:
490
     */
491
    editPost(postId, message, options) {
492
        return Post.edit(this, postId, message, options);
493
    }
494
495
    /**
496
     * Warning untested - does not seem to function yet
497
     * Attempts to delete an existing Post
498
     * @param {number} postId - Post id
499
     * @param {number} threadId - Thread id
500
     * @param {object=} options
501
     * @param {string=} options.reason - Reason for deleting
502
     * @param {number=} options.postid - Ignore, already required at postId
503
     * @param {number=} options.threadid - Ignore, already required at threadId
504
     * @returns {Promise<*>} - Returns a unhandled response currently
505
     * @fulfill {*}
506
     * @reject {string} - Error Reason. Expects:
507
     */
508
    deletePost(postId, threadId, options) {
509
        return Post.delete(this, postId, threadId, options);
510
    }
511
512
    /**
513
     * List detailed information about a Thread and it's Posts
514
     * @param {number} threadId - Thread id
515
     * @param {object=} options - Secondary Options
516
     * @param {number=} options.threadid - Ignore, already required at threadId
517
     * @returns {Promise<Thread>} - Returns a Thread object
518
     * @fulfill {Thread}
519
     * @reject {string} - Error Reason. Expects:
520
     */
521
    getThread(threadId, options) {
522
        return Thread.get(this, threadId, options);
523
    }
524
525
    /**
526
     * Attempts to submit a new Thread into a specified Forum. This will also be considered the first Post
527
     * @param {number} forumId - Forum Id
528
     * @param {string} subject - Post/Thread Subject
529
     * @param {string} message - Post Message
530
     * @param {object=} options
531
     * @param {boolean=} options.signature - Optionally append your signature
532
     * @param {number=} options.forumid - Ignore, already required at postId
533
     * @param {string=} options.subject - Ignore, already required at postId
534
     * @param {string=} options.message - Ignore, already required at postId
535
     * @returns {Promise<*>} - Returns a unhandled response currently
536
     * @fulfill {*}
537
     * @reject {string} - Error Reason. Expects:
538
     */
539
    createThread(forumId, subject, message, options) {
540
        return Thread.create(this, forumId, subject, message, options);
541
    }
542
543
    /**
544
     * @deprecated as of 1.4.1
545
     * @see createThread
546
     */
547
    newThread(forumId, subject, message, options) {
548
        return Thread.create(this, forumId, subject, message, options);
549
    }
550
551
    /**
552
     * Warning untested - may not function yet
553
     * Attempts to close a specific Thread. Requires a user to have a 'inline mod' permissions
554
     * @param {number} threadId - Id of Thread to close
555
     * @returns {Promise<*>} - Returns a unhandled response currently
556
     * @fulfill {*}
557
     * @reject {string} - Error Reason. Expects:
558
     */
559
    closeThread(threadId) {
560
        return Thread.close(this, threadId);
561
    }
562
563
    /**
564
     * Warning incomplete - may not function yet
565
     * Attempts to open a specific Thread. Requires a user to have a 'inline mod' permissions
566
     * @param {number} threadId - Id of Thread to open
567
     * @returns {Promise<*>} - Returns a unhandled response currently
568
     * @fulfill {*}
569
     * @reject {string} - Error Reason. Expects:
570
     */
571
    openThread(threadId) {
572
        return Thread.open(this, threadId);
573
    }
574
575
    /**
576
     * Warning incomplete - may not function yet
577
     * Attempts to delete a specific Thread. Requires a user to have a 'inline mod' permissions
578
     * @param {number} threadId - Id of Thread to close
579
     * @returns {Promise<*>} - Returns a unhandled response currently
580
     * @fulfill {*}
581
     * @reject {string} - Error Reason. Expects:
582
     */
583
    deleteThread(threadId) {
584
        return Thread.delete(this, threadId);
585
    }
586
587
    /**
588
     * @deprecated as of 1.3.1
589
     * @see closeThread
590
     */
591
    modCloseThread(threadId) {
592
        return Thread.close(this, threadId);
593
    }
594
595
    /**
596
     * @deprecated as of 1.3.1
597
     * @see openThread
598
     */
599
    modOpenThread(threadId) {
600
        return Thread.open(this, threadId);
601
    }
602
603
    /**
604
     * @deprecated as of 1.3.1
605
     * @see deleteThread
606
     */
607
    modDeleteThread(threadId) {
608
        return Thread.delete(this, threadId);
609
    }
610
611
    /**
612
     * Get logged in user's Inbox and list of private Messages
613
     * @param {object=} options
614
     * @returns {Promise<Inbox>} - Returns an Inbox object
615
     * @fulfill {Inbox}
616
     * @reject {string} - Error Reason. Expects:
617
     */
618
    getInbox(options) {
619
        return Inbox.get(this, options);
620
    }
621
622
    /**
623
     * Attempts to submit a new Thread into a specified Forum. This will also be considered the first Post
624
     * @param {Date} date - Delete all messages from before the specified date
625
     * @param {number=0} folderId - Folder Id, defaults to 0
626
     * @param {object=} options
627
     * @param {string=} options.dateline - Ignore, already required at date
628
     * @param {number=} options.folderid - Ignore, already required at folderId
629
     * @returns {Promise<void>} - Returns a unhandled response currently
630
     * @fulfill {void}
631
     * @reject {string} - Error Reason. Expects:
632
     */
633
    emptyInbox(date, folderId, options) {
634
        return Inbox.empty(this, date, folderId, options)
635
    }
636
637
    /**
638
     * Get details of a specific Message for the logged in user
639
     * @param {number} id
640
     * @param {object=} options
641
     * @param {number=} options.pmid - Ignore, already required at id
642
     * @returns {Promise<Message>} - Returns a Message object
643
     * @fulfill {Message}
644
     * @reject {string} - Error Reason. Expects:
645
     */
646
    getMessage(id, options) {
647
        return Message.get(this, id, options);
648
    }
649
650
    /**
651
     *
652
     * @param {string} username - Username to send the message to
653
     * @param {string} title - Message Subject
654
     * @param {string} message - Message content
655
     * @param {object=} options
656
     * @param {boolean=} options.signature - Optionally append your signature
657
     * @param {string=} options.recipients - Ignore, already required at username
658
     * @param {string=} options.title - Ignore, already required at title
659
     * @param {string=} options.message - Ignore, already required at message
660
     * @returns {Promise<void>} - Successfully completes if sent.
661
     * @fulfill {void}
662
     * @reject {string} - Error Reason. Expects:
663
     */
664
    createMessage(username, title, message, options) {
665
        return Message.create(this, username, title, message, options)
666
    }
667
668
    /**
669
     * @deprecated as of 1.4.1
670
     * @see createMessage
671
     */
672
    sendMessage(username, title, message, options) {
673
        return Message.create(this, username, title, message, options)
674
    }
675
676
    /**
677
     * Attempts to retrieve data about a specific user found by username
678
     * @param {string} username - Username
679
     * @param {object=} options - Secondary Options
680
     * @param {string=} options.username - Ignore, already required at username
681
     * @returns {Promise<Member>} - Returns a Member object
682
     * @fulfill {Member}
683
     * @reject {string} - Error Reason. Expects:
684
     */
685
    getMember(username, options) {
686
        return Member.get(this, username, options);
687
    }
688
}
689
690
module.exports = VBApi;
691